﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using XnaUtil;

namespace ModelViewer
{
	public class Bone : IPlaceable
    {
        #region Fields

        /// <summary>
        /// Any parent for this bone
        /// </summary>
        private Bone parent = null;

        /// <summary>
        /// The children of this bone
        /// </summary>
        private List<Bone> children = new List<Bone>();

        /// <summary>
        /// The bind transform is the transform for this bone
        /// as loaded from the original model. It's the base pose.
        /// </summary>
        private Matrix bindTransform = Matrix.Identity;

        /// <summary>
        /// Is this bone enabled for drawing?
        /// </summary>
        private bool enabled = true;

        /// <summary>
        /// Bone translation
        /// </summary>
        private Vector3 translation = Vector3.Zero;

        /// <summary>
        /// Bone rotation
        /// </summary>
        private Vector3 rotation = Vector3.Zero;

        /// <summary>
        /// Bone scale
        /// </summary>
        private Vector3 scale = Vector3.One;

        #endregion 

        #region Properties

        /// <summary>
        /// The bone name
        /// </summary>
		public string Name = "";

        /// <summary>
        /// The bone bind transform
        /// </summary>
        public Matrix BindTransform { get { return bindTransform; } }

        /// <summary>
        /// Inverse of absolute bind transform for skinnning
        /// </summary>
        public Matrix SkinTransform { get; set; }

        /// <summary>
        /// Any Euler angle rotations
        /// </summary>
		public Vector3 Rotation {get {return rotation;} set {rotation = value;}}

        /// <summary>
        /// Any translations
        /// </summary>
		public Vector3 Translation {get {return translation;} set {translation = value;}}

        /// <summary>
        /// Any scaling
        /// </summary>
        public Vector3 Scale { get { return scale; } set { scale = value; } }

        /// <summary>
        /// The parent bone or NULL for the root bone
        /// </summary>
        public Bone Parent
        {
            get { return parent; }
            set { parent = value; parent.children.Add(this); }
        }

        /// <summary>
        /// The children of this bone
        /// </summary>
        public List<Bone> Children { get { return children; } }

        /// <summary>
        /// The bone absolute transform
        /// </summary>
        public Matrix AbsoluteTransform = Matrix.Identity;

        /// <summary>
        /// Is this bone enabled?
        /// </summary>
        public bool Enabled { get { return enabled; } set { enabled = value; } }

        #endregion

        #region Operations

        public Bone(string name, Matrix bindTransform)
        {
            this.Name = name;
            this.bindTransform = bindTransform;
        }

        public void ComputeAbsoluteTransform()
        {
            Matrix transform = Matrix.CreateScale(Scale) *
                Matrix.CreateRotationX(Rotation.X) *
                Matrix.CreateRotationY(Rotation.Y) *
                Matrix.CreateRotationZ(Rotation.Z) *
                Matrix.CreateTranslation(Translation) *
                BindTransform;

            if (Parent != null)
            {
                // This bone has a parent bone
                AbsoluteTransform = transform * Parent.AbsoluteTransform;
            }
            else
            {   // The root bone
                AbsoluteTransform = transform;
            }
        }

        public void ResetAll()
        {
            Translation = Vector3.Zero;
            Rotation = Vector3.Zero;
            Scale = Vector3.One;
            Enabled = true;

            foreach (Bone bone in Children)
            {
                bone.ResetAll();
            }
        }

        public void SetEnabled(bool enabled)
        {
            Enabled = enabled;

            foreach (Bone bone in Children)
            {
                bone.SetEnabled(enabled);
            }
        }

        /// <summary>
        /// This sets the scale, rotation, and translation such that the
        /// scale  times the rotation times the translation times the bind after set
        /// equals this matrix. This is used to set animation values.
        /// </summary>
        /// <param name="m"></param>
        public void SetCompleteTransform(Matrix m)
        {
            Matrix setTo = m * Matrix.Invert(BindTransform);

            Scale = new Vector3(setTo.Right.Length(), setTo.Up.Length(), setTo.Backward.Length());
            setTo.Right /= Scale.X;
            setTo.Up /= Scale.Y;
            setTo.Backward /= Scale.Z;
            Translation = setTo.Translation;
            Quaternion q = Quaternion.CreateFromRotationMatrix(setTo);
            Rotation = EulerHelper.QuaternionToXyz(q);
        }

        /// <summary>
        /// This computes a scale, rotation, and translation such that the
        /// scale  times the rotation times the translation times the bind after set
        /// would be equal to this matrix. This is used by the animation export system.
        /// </summary>
        /// <param name="m">Matrix we want to equal</param>
        /// <param name="scale">Computed Scale</param>
        /// <param name="rotation">Computed Rotation</param>
        /// <param name="translation">Computed Translation</param>
        public void GetCompleteTransform(Matrix m, out Vector3 scale, out Quaternion rotation, out Vector3 translation)
        {
            Matrix setTo = m * Matrix.Invert(BindTransform);

            scale = new Vector3(setTo.Right.Length(), setTo.Up.Length(), setTo.Backward.Length());
            setTo.Right /= scale.X;
            setTo.Up /= scale.Y;
            setTo.Backward /= scale.Z;
            translation = setTo.Translation;
            rotation = Quaternion.CreateFromRotationMatrix(setTo);
        }

        #endregion
    }
}
